#include <thread>
#include <chrono>
#include <sstream>
#include <fstream>
#include <list>

#ifdef _WIN32
#include <winsock2.h>
#else  // some Unix-like OS
#include <arpa/inet.h>
#endif

#include <boost/asio.hpp>
#include <boost/asio/detail/socket_option.hpp>
#include <boost/system/system_error.hpp>
#include <boost/array.hpp>

#include <boost/asio/thread_pool.hpp>
#include <boost/endian/conversion.hpp>

#include <drogon/drogon.h>
#include <drogon/WebSocketController.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/WebSocketController.h>

using namespace drogon;
boost::asio::thread_pool pool(std::thread::hardware_concurrency());
std::list<WebSocketConnectionPtr> websoket_connection;

#include <cmd/discovery.h>
#include <capture/capture.hpp>
#include <sensor/sensor.hpp>
//#include <data/database_td.h>

std::vector<unsigned char> Json2Array(Json::Value& root){
    std::vector<unsigned char> ret;
    if(root.isArray()){
        for (Json::ArrayIndex i = 0; i < root.size(); ++i)
            ret.emplace_back(root[i].asUInt());
    }
    return ret;
}
std::mutex mx;
class WebSocketData : public drogon::WebSocketController<WebSocketData>
{
public:
    void handleNewMessage(const WebSocketConnectionPtr& wsConnPtr, std::string&& message, const WebSocketMessageType& type) override {}
    void handleConnectionClosed(const WebSocketConnectionPtr& conn) override {
        //BOOST_LOG_TRIVIAL(info) << "websocket connection close";
        std::lock_guard<std::mutex> lock(mx);
        websoket_connection.remove(conn);
    }
    void handleNewConnection(const HttpRequestPtr& req, const WebSocketConnectionPtr& conn) override {
        //BOOST_LOG_TRIVIAL(info) << "websocket connection reset(" << websoket_board_id << ")";
        std::lock_guard<std::mutex> lock(mx);
        websoket_connection.emplace_back(conn);
    }
    WS_PATH_LIST_BEGIN
        WS_PATH_ADD("/chart", Get);
        WS_ADD_PATH_VIA_REGEX("/[^/]*", Get);
    WS_PATH_LIST_END
};
int main(int argc, char *argv[])
{
    //if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
    //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
    //MainWindow w2;
    //w2.show();

    //SampleConfigWnd SetSampleParamw;
    //SetSampleParamw.show();
    try{
        if(!std::filesystem::exists("config.ini")){
            std::ofstream fout("config.ini");
            fout.close();
        }
        boost::property_tree::ini_parser::read_ini("config.ini", settings);
        std::thread([]{
            mqttplus::inst();
            rabbitmqplus::inst();
        }).detach();
        Discovery discovery;
        int tcp_port = settings.get<int>("sensor.port",3654);
        settings.put("sensor.port",tcp_port);
        int http_port = settings.get<int>("http.port", 80);
        settings.put("http.port",http_port);
        std::string device_host = settings.get<std::string>("capture.host", "192.168.10.166");
        settings.put("capture.host",device_host);
        int device_port = settings.get<int>("capture.port", 502);
        settings.put("capture.port",device_port);
        std::thread([=]{
            try{
                static boost::asio::io_service io_service;
                static auto work = boost::asio::make_work_guard(io_service);
                while(true){
                    if(capture::sockets.size()==0){
                        boost::system::error_code ec;
                        boost::asio::ip::tcp::socket* socket = new boost::asio::ip::tcp::socket(io_service);
                        socket->connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(device_host), device_port),ec);
                        if(!ec){
                            capture::sockets[socket]=std::map<unsigned short,std::tuple<std::vector<unsigned char>,bool>>();
                            capture::recv_thread(capture::sockets.find(socket));
                            BOOST_LOG_TRIVIAL(info) <<"capture " <<socket->remote_endpoint(ec).address().to_string()<<":"<<std::dec<<std::dec<<socket->remote_endpoint(ec).port()<<" connect success.";
                        }else{
                            std::this_thread::sleep_for(std::chrono::seconds(3));
                            delete socket;
                        }
                    }else{
                        std::unique_lock<std::mutex> lk(capture::mw);
                        capture::cv.wait(lk);
                    }
                }
            }catch(...){
                BOOST_LOG_TRIVIAL(error) << "capture connect fail.";
            }
        }).detach();
        std::thread([=]{
            try{
                boost::system::error_code ec;
                boost::asio::io_service io_service;//asio 消息循环控制器
                auto work = boost::asio::make_work_guard(io_service);
                boost::asio::ip::tcp::acceptor acceptor(io_service,
                    boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), tcp_port));
                BOOST_LOG_TRIVIAL(info) << "sensor service start success.";//日志记录
                while (true) {
                    boost::asio::ip::tcp::socket *socket=new boost::asio::ip::tcp::socket(io_service);
                    acceptor.accept(*socket);//接收套接字
                    {
                        std::lock_guard<std::mutex> lock(sensor::mw);
                        sensor::sockets[socket]=std::make_tuple("",false);//套接字缓存
                    }
                    BOOST_LOG_TRIVIAL(info) << "sensor "<<socket->remote_endpoint(ec).address().to_string()<<":"<<std::dec
                        <<socket->remote_endpoint(ec).port()<<" connect success.";
                    boost::system::error_code ignored_error;
                    auto pack_data = sensor::make_package(1,0x10,{});
                    socket->write_some(boost::asio::buffer(pack_data),ignored_error);//发送请求
                    sensor::recv_thread(sensor::sockets.find(socket));//接收线程
                }
            }catch(...){
                BOOST_LOG_TRIVIAL(error)<< "sensor service start fail.";
            }
        }).detach();
        try{
            app().reusePort();
            app().addListener("0.0.0.0", settings.get<int>("http.port", http_port)).setDocumentRoot(settings.get<std::string>("http.path", "page"));
            settings.put("http.port",settings.get<int>("http.port", http_port));
            settings.put("http.path",settings.get<std::string>("http.path", "page"));
            app().registerSyncAdvice([](const HttpRequestPtr& req) -> HttpResponsePtr
            {
                if(req->method() == drogon::HttpMethod::Options)
                {
                    auto resp = HttpResponse::newHttpResponse();
                    {
                        const auto& val = req->getHeader("Origin");
                        if(!val.empty())
                            resp->addHeader("Access-Control-Allow-Origin", val);
                    }
                    {
                        const auto& val = req->getHeader("Access-Control-Request-Method");
                        if(!val.empty())
                            resp->addHeader("Access-Control-Allow-Methods", val);
                    }
                    resp->addHeader("Access-Control-Allow-Credentials", "true");
                    {
                        const auto& val = req->getHeader("Access-Control-Request-Headers");
                        if(!val.empty())
                            resp->addHeader("Access-Control-Allow-Headers", val);
                    }
                    return std::move(resp);
                }
                return {};
            })
            .registerPostHandlingAdvice([](const HttpRequestPtr& req, const HttpResponsePtr& resp) -> void
            {
                {
                    const auto& val = req->getHeader("Origin");
                    if(!val.empty())
                        resp->addHeader("Access-Control-Allow-Origin", val);
                }
                {
                    const auto& val = req->getHeader("Access-Control-Request-Method");
                    if(!val.empty())
                        resp->addHeader("Access-Control-Allow-Methods", val);
                }
                resp->addHeader("Access-Control-Allow-Credentials", "true");
                {
                    const auto& val = req->getHeader("Access-Control-Request-Headers");
                    if(!val.empty())
                        resp->addHeader("Access-Control-Allow-Headers", val);
                }
            })
            .setExceptionHandler([](const std::exception& e,const HttpRequestPtr& req,std::function<void(const HttpResponsePtr &)> &&callback){
                BOOST_LOG_TRIVIAL(error)<< "exception: "<< e.what();
                auto resp = HttpResponse::newHttpResponse();
                resp->setBody(R"({"state":1})");
                callback(resp);
            })
            .registerHandler("/signin", [&](const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback) {
                auto resp = HttpResponse::newHttpResponse();
                settings.put<std::string>("login.user", req->getParameter("name"));
                settings.put<std::string>("login.password", req->getParameter("password"));
                boost::property_tree::ini_parser::write_ini("config.ini", settings);
                resp->setBody(std::string(R"({"state":0})"));
                callback(resp);
            }, { Get })
            .registerHandler("/login", [&](const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback) {
                auto resp = HttpResponse::newHttpResponse();
                if (settings.get<std::string>("login.user", "admin") == req->getParameter("user") &&
                    settings.get<std::string>("login.password", "e10adc3949ba59abbe56e057f20f883e") == req->getParameter("password"))
                    resp->setBody(std::string(R"({"state":0})"));
                else
                    resp->setBody(std::string(R"({"state":1})"));
                callback(resp);
            }, { Get })
            .registerHandler("/ip_list", [&](const HttpRequestPtr&, std::function<void(const HttpResponsePtr&)>&& callback) {
                Json::Value json_ret;
                Json::FastWriter writer;
                auto resp = HttpResponse::newHttpResponse();
                json_ret["state"]=0;
                for(auto ip : discovery.getips()){
                    Json::Value item;
                    item["ip"] = ip;
                    json_ret["data"].append(item);
                }
                resp->setBody(writer.write(json_ret));
                callback(resp);
            }, { Get })
            .registerHandler("/device_list", [&](const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback) {
                auto resp = HttpResponse::newHttpResponse();
                Json::Value json_ret;
                Json::FastWriter writer;
                json_ret["state"]=0;
                for(auto it=sensor::sockets.begin();it!=sensor::sockets.end();++it){
                    boost::system::error_code ignored_error;
                    auto pack_data = sensor::make_package(1,0x10,{});
                    it->first->write_some(boost::asio::buffer(pack_data),ignored_error);
                    auto id = std::distance(sensor::sockets.begin(),it)+SENSOR_ID_BASE;
                    sensor::recv(it,0x10,[&](int code,const std::vector<unsigned char>& data){
                        if(code ==0x10 && data.size()>0){
                            ((std::vector<unsigned char>&)data).emplace_back(0);
                            std::vector<std::string> vars;
                            boost::algorithm::split(vars,(char*)&data[0],boost::is_any_of(","));
                            {
                                std::lock_guard<std::mutex> lock(sensor::mw);
                                std::get<0>(it->second) = vars[0];
                            }
                            Json::Value item;
                            item["name"]=vars[0];
                            item["id"]=std::to_string(id);
                            item["type"]="sensor";
                            item["firmware"]=vars[3];
                            item["method"]=vars[2];
                            item["freq"]=vars[4];
                            item["status"]=std::get<1>(it->second)?"启动":"停止";
                            item["board"]=vars[1];
                            json_ret["data"].append(item);
                        }
                    });
                }
                if(capture::sockets.size()>0){
                    auto socket_it = capture::sockets.begin()->second;
                    for(auto cp : socket_it)
                    {
                        Json::Value item;
                        switch(amc::type(std::get<0>(cp.second))){
                        case 0x7376:{item["name"]="IEPE采集板";item["channel"]=6;break;}
                        case 0x7377:{item["name"]="电流采集板";item["channel"]=16;break;}
                        case 0x7511:{item["name"]="开关量输入采集板";item["channel"]=32;break;}
                        case 0x7378:{item["name"]="铂电阻采集板";item["channel"]=16;break;}
                        case 0x7512:{item["name"]="转速输入采集板";item["channel"]=32;break;}
                        default:continue;
                        }
                        item["id"]=std::to_string(cp.first);
                        item["type"]="capture";
                        item["firmware"]=amc::version(std::get<0>(cp.second));
                        item["method"]=AMC(trigger,std::get<0>(cp.second))?"软件触发":"背板触发";
                        item["freq"]=AMC(sampling,std::get<0>(cp.second));
                        item["status"]=std::get<1>(cp.second)?"启动":"停止";
                        std::stringstream ss;
                        ss<<std::hex<<"0x"<<amc::type(std::get<0>(cp.second));
                        item["board"]=ss.str();
                        json_ret["data"].append(item);
                    }
                }
                resp->setBody(writer.write(json_ret));
                callback(resp);
            }, { Get })
            .registerHandler("/query", [&](const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback) {
                auto resp = HttpResponse::newHttpResponse();
                auto it = sensor::sockets.begin();
                std::advance(it,atoi(req->getParameter("id").c_str())-SENSOR_ID_BASE);
                Json::Value json_ret;
                Json::FastWriter writer;
                json_ret["state"]=0;
                if (it != sensor::sockets.end()) {
                    boost::system::error_code ignored_error;
                    auto pack_data =sensor::make_package(1,0x10,{});
                    it->first->write_some(boost::asio::buffer(pack_data),ignored_error);
                    auto id = std::distance(sensor::sockets.begin(),it)+SENSOR_ID_BASE;
                    sensor::recv(it,0x10,[&](int code,const std::vector<unsigned char>& data){
                        BOOST_LOG_TRIVIAL(info)<<"0x"<<std::hex<<code<<":"<<serilize(data);
                        if(code ==0x10 && data.size()>0){
                            ((std::vector<unsigned char>&)data).emplace_back(0);
                            std::vector<std::string> vars;
                            boost::algorithm::split(vars,(char*)&data[0],boost::is_any_of(","));
                            Json::Value item;
                            item["name"]=vars[0];
                            item["id"]=std::to_string(id);
                            item["type"]="board";
                            item["firmware"]=vars[3];
                            item["method"]=vars[2];
                            item["freq"]=vars[4];
                            item["status"]=std::get<1>(it->second)?"启动":"停止";
                            item["board"]=vars[1];
                            json_ret["data"].append(item);
                            resp->setBody(writer.write(json_ret));
                        }else if(code=0){
                            resp->setBody((R"({"state":1})"));
                        }
                    });
                }else if(capture::sockets.size()&&capture::sockets.begin()->second.find(atoi(req->getParameter("id").c_str()))!=capture::sockets.begin()->second.end()){
                    Json::Value item;
                    auto device = capture::sockets.begin()->second.find(atoi(req->getParameter("id").c_str()));
                    switch(amc::type(std::get<0>(device->second))){
                    case 0x7376:{item["name"]="IEPE采集板";item["channel"]=6;break;}
                    case 0x7377:{item["name"]="电流采集板";item["channel"]=16;break;}
                    case 0x7511:{item["name"]="开关量输入采集板";item["channel"]=32;break;}
                    case 0x7378:{item["name"]="铂电阻采集板";item["channel"]=16;break;}
                    case 0x7512:{item["name"]="转速输入采集板";item["channel"]=32;break;}
                    default:{json_ret["name"]="unknow";item["channel"]=1;break;}
                    }
                    item["id"]=std::to_string(device->first);
                    item["type"]="capture";
                    item["firmware"]=amc::version(std::get<0>(device->second));
                    item["method"]=AMC(trigger,std::get<0>(device->second))==1?"软件触发":"背板触发";
                    item["freq"]=AMC(sampling,std::get<0>(device->second));
                    item["status"]=std::get<1>(device->second)?"启动":"停止";
                    std::stringstream ss;
                    ss<<std::hex<<"0x"<<amc::type(std::get<0>(device->second));
                    item["board"]=ss.str();
                    json_ret["data"].append(item);
                }else{
                    resp->setBody((R"({"state":1,"data":"can not found device"})"));
                }
                callback(resp);
            }, { Get });
            sensor::init();
            capture::init();
            app().setIntSignalHandler([] {
                boost::property_tree::write_ini("config.ini", settings);
                app().quit();
            })
            .setTermSignalHandler([] {
                boost::property_tree::write_ini("config.ini", settings);
                app().quit();
            })
            //.setThreadNum(2)
            .run();
        }catch(std::exception e){
            BOOST_LOG_TRIVIAL(error)<<e.what();
        }
        return 0;
    }catch(std::exception ex){
        BOOST_LOG_TRIVIAL(error)<<ex.what();
        return 0;
    }catch(...){
        BOOST_LOG_TRIVIAL(error)<<"exception";
        return 0;
    }
}
